# -*- coding: utf-8 -*-
"""
    APF.core.plugin_manager
    ~~~~~~~~~~~~~~
    插件管理器
    :copyright: (c) 2018 XJJ by CornWu
"""

import os
import imp
import json
from flask import Flask,Blueprint,request,make_response,render_template
from flasgger import Swagger,swag_from,validate

from utils.tool import logger
from utils.dynamic_dict import DynamicDict
from libs import plugin_base
from config import GLOBAL

"""
    .ApiPluginsFramework
        │---Plugins
                │
                │---Plugin1(插件目录)
                │   ├── __init__.py（插件定义文件）
                │   └── plugin_moduleX.py（插件模块文件）
                │
                │---Plugin2(插件目录)
                │   ├── __init__.py（插件定义文件）
                │   └── plugin_moduleX.py（插件模块文件）
"""
class PluginManager(object):
    def __init__(self, app):
        self.app = app
        self.plugins = []
        self.plugins_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "plugins")
        self.plugins_functions = {}
        self.__scanPlugins()
        #self.__regeditPluginSwagger()
        self.__regeditPlugin()
        self.ActivatePlugin()

    #: 扫描插件
    def __scanPlugins(self):
        """ 扫描插件目录 """
        logger.plugin.info("Initialization Plugins Start, loadPlugins path: {0}".format(self.plugins_path))
        if os.path.exists(self.plugins_path):
            for pluginName in os.listdir(self.plugins_path):
                _plugin_path = os.path.join(self.plugins_path, pluginName)
                if os.path.isdir(_plugin_path):
                    pluginFilePath = os.path.join(_plugin_path, "__init__.py")
                    if os.path.isfile(pluginFilePath):
                        logger.plugin.info("find plugin package: {0},path:{1}".format(pluginName,pluginFilePath))
                        self.__loadPlugin(pluginName,_plugin_path,pluginFilePath)
        else:
            logger.plugin.warning("Plugins directory not in here!")

    #: 加载插件
    def __loadPlugin(self, pluginName,pluginPath,pluginFilePath):
        """ 动态加载插件模块,遵循插件格式的才能被启用并运行,否则删除加载 """
        try:
            plugin_base.__ModuleFunctions__ = {}
            #: 动态加载模块(plugins.pluginName): 可以查询自定义的信息, 并通过getPluginClass获取插件的类定义
            plugin = imp.load_source("{0}{1}".format("plugins", pluginName), pluginFilePath)

            #: 获取插件主类并实例化
            #p = plugin.getPluginClass()
            #plugin_instance = p(pluginName)
            plugin_instance = plugin.createPluginInstance(pluginName,pluginPath)

            #: 初始化插件
            if plugin_instance.Initialize() == False:
                logger.plugin.error("plugin package: {0} Initialize Error".format(pluginName))
                return

            pluginInfo = {
                "plugin_package": plugin,
                "plugin_name": pluginName,
                "plugin_path": pluginPath,
                "plugin_file_path": pluginFilePath,
                "plugin_instance": plugin_instance
            }
            #: 获取插件关于信息
            pluginAbout = plugin_instance.GetAbout()
            if pluginAbout is not None:
                pluginInfo.update(plugin_about=pluginAbout)
            #: 获取插件功能信息
            pluginFuncList = plugin_instance.GetFuncList()
            if pluginFuncList is not None:
                pluginInfo.update(plugin_funclist=pluginFuncList)

            self.plugins.append(pluginInfo)
        except Exception,e:
            logger.plugin.error("plugin package: {0} load error,{1}".format(pluginName, e))
            logger.plugin.exception(e, exc_info=True)
            return

    #: 检测插件
    def __checkPlugin(self, pluginName, plugin_instance):
        #: 检查插件方法是否存在
        self.__checkPluginMethod(pluginName, plugin_instance, "Initialize")
        self.__checkPluginMethod(pluginName, plugin_instance, "Release")
        self.__checkPluginMethod(pluginName, plugin_instance, "Activate")
        self.__checkPluginMethod(pluginName, plugin_instance, "Deactivate")
        self.__checkPluginMethod(pluginName, plugin_instance, "GetAbout")
        self.__checkPluginMethod(pluginName, plugin_instance, "GetFuncList")

    #: 检测插件方法名是否合法
    def __checkPluginMethod(self, pluginName, plugin_instance, name):
        if hasattr(plugin_instance, name) == False:
            logger.plugin.error("plugin package: {0} is not '{1}' method".format(pluginName, name))
            return False

        return True

    #: 注册插件Swagger
    def __regeditPluginSwagger(self):
        self.SwaggerTemplate = {
            "swagger": "2.0",
            "tags": []
        }
        paths = {}
        for plugin in self.plugins:
            try:
                plugin_package = plugin["plugin_package"]
                plugin_name = plugin["plugin_name"]
                plugin_path = plugin["plugin_path"]
                plugin_file_path = plugin["plugin_file_path"]
                plugin_funclist = plugin["plugin_funclist"]
                plugin_about = plugin["plugin_about"]

                #: 注册Swagger
                self.SwaggerTemplate["tags"].append({
                    "name": plugin_name,
                    "description": plugin_about["plugin_description"]
                })

                for funcName in plugin_funclist:
                    funcObject = plugin_funclist[funcName]
                    url = "/{0}/{1}{2}".format(plugin_name,funcObject["ModuleName"], funcObject["ModuleRule"])

                    ApiSpecs = funcObject["ApiSpecs"]
                    if ApiSpecs is not None:
                        paths[url] = {
                            "get" : ApiSpecs
                        }
            except Exception,e:
                logger.plugin.error("plugin package: {0} regedit Swagger,{1}".format(plugin["pluginName"], e))
                logger.plugin.exception(e, exc_info=True)
                return
        self.SwaggerTemplate["paths"] = paths
        self.Swagger = Swagger(self.app, template=self.SwaggerTemplate)

    #: 注册插件
    def __regeditPlugin(self):
        self.SwaggerTemplate = {
            "swagger": "2.0",
            "tags": [],
            "paths": {}
        }

        for plugin in self.plugins:
            try:
                plugin_package = plugin["plugin_package"]
                plugin_name = plugin["plugin_name"]
                plugin_path = plugin["plugin_path"]
                plugin_file_path = plugin["plugin_file_path"]
                plugin_funclist = plugin["plugin_funclist"]
                plugin_about = plugin["plugin_about"]

                #: 注册Swagger 模块
                self.SwaggerTemplate["tags"].append({
                    "name": plugin_name,
                    "description": plugin_about["plugin_description"]
                })

                #: 注册Flask
                plugin_blueprint = Blueprint(plugin_name, plugin_name)
                for funcName in plugin_funclist:
                    funcObject = plugin_funclist[funcName]
                    self.plugins_functions[funcName] = funcObject

                    url = "/{0}{1}".format(funcObject["ModuleName"], funcObject["ModuleRule"])
                    #plugin_blueprint.add_url_rule(url,view_func=funcObject["ModuleFunc"], methods=["GET"])
                    plugin_blueprint.add_url_rule(url, view_func=self.__plugin_dispatch_request, methods=funcObject["Methods"])

                    #: 注册Swagger 功能
                    func_url = "/{0}/{1}{2}".format(plugin_name, funcObject["ModuleName"], funcObject["ModuleRule"])
                    '''
                    ApiSpecs = funcObject["ApiSpecs"]
                    if ApiSpecs is not None:
                        for verb in funcObject["Methods"]:
                            self.SwaggerTemplate["paths"][func_url] = {
                                verb.lower() : ApiSpecs
                            }
                    '''
                    ApiSpecs = {}
                    ApiSpecs["summary"] = funcObject["ApiOperation"]["Name"]
                    ApiSpecs["description"] = funcObject["ApiOperation"]["Description"]
                    ApiSpecs["tags"] = [plugin["plugin_name"]]

                    #. 生成请求参数列表
                    Parameters = []
                    ApiParameter = funcObject["ApiParameter"]
                    if ApiParameter is not None:
                        Parameter = {
                            "name": "body",
                            "in": "body",
                            "required": "true",
                            "description": ApiParameter["Description"] if (ApiParameter["Description"] != None) else "",
                            "schema": {
                                "id": "{0}-{1}-{2}".format(funcObject["PluginName"], funcObject["ModuleName"], funcObject["FuncName"]),
                                "type": "object",
                                "properties": {}
                            }
                        }
                        Properties = ApiParameter["Properties"] if (ApiParameter["Properties"] != None) else {}

                        #. 生成字段列表
                        def GenProperties(Properties):
                            Required = []
                            ParameterProperties = {}
                            for PropertieName in Properties:
                                Property = Properties[PropertieName]
                                PropertyDataType = Property["DataType"] if (Property.has_key("DataType")) else "string"
                                PropertyDescription = Property["Description"] if (Property.has_key("Description")) else ""
                                PropertyRequired = Property["Required"] if (Property.has_key("Required")) else True
                                PropertieEnum = Property["Enum"] if (Property.has_key("Enum")) else None
                                PropertyProperties = Property["Properties"] if (Property.has_key("Properties")) else {}
                                PropertyItems = Property["Items"] if (Property.has_key("Items")) else None
                                if PropertyRequired == True:
                                    Required.append(PropertieName)

                                ParameterProperties.update({
                                    PropertieName: {
                                    "type": PropertyDataType,
                                    "description": PropertyDescription,
                                    }
                                })
                                if PropertieEnum is not None:
                                    ParameterProperties[PropertieName].update(enum=PropertieEnum)

                                if PropertyDataType == "object":
                                    ChildRequired,ChildParameterProperties = GenProperties(PropertyProperties)
                                    ParameterProperties[PropertieName].update(required = ChildRequired)
                                    ParameterProperties[PropertieName].update(properties=ChildParameterProperties)
                                elif PropertyDataType == "array" and PropertyItems is not None:
                                    ItemDataType = PropertyItems["DataType"] if (PropertyItems.has_key("DataType")) else "string"
                                    Items = {
                                        "type": ItemDataType
                                    }
                                    if ItemDataType == "object":
                                        ItemProperties = PropertyItems["Properties"] if (PropertyItems.has_key("Properties")) else {}
                                        ItemRequired, ItemParameterProperties = GenProperties(ItemProperties)
                                        Items.update(required = ItemRequired)
                                        Items.update(properties=ItemParameterProperties)

                                    ParameterProperties[PropertieName].update(items=Items)


                            return Required,ParameterProperties

                        Required,ParameterProperties = GenProperties(Properties)
                        Parameter["schema"].update(required = Required)
                        Parameter["schema"].update(properties=ParameterProperties)

                        Parameters.append(Parameter)

                    #. 生成应答参数列表
                    Responses = {
                        "200": {
                            "description": "返回结果",
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "resultCode": {
                                        "type": "integer",
                                        "description": "返回码"
                                    },
                                    "resultMsg": {
                                        "type": "string",
                                        "description": "返回描述"
                                    },
                                    "resultData": {
                                    }
                                }
                            }
                        }
                    }
                    ApiResponse = funcObject["ApiResponse"]
                    if ApiResponse is not None:
                        #. 生成应答字段列表
                        def GenResponseProperties(Properties):
                            ParameterProperties = {}
                            for PropertieName in Properties:
                                Property = Properties[PropertieName]
                                PropertyDataType = Property["DataType"] if (Property.has_key("DataType")) else "string"
                                PropertyDescription = Property["Description"] if (Property.has_key("Description")) else ""
                                PropertyProperties = Property["Properties"] if (Property.has_key("Properties")) else {}
                                PropertyItems = Property["Items"] if (Property.has_key("Items")) else None

                                ParameterProperties.update({
                                    PropertieName: {
                                    "type": PropertyDataType,
                                    "description": PropertyDescription,
                                    }
                                })

                                if PropertyDataType == "object":
                                    ChildParameterProperties = GenResponseProperties(PropertyProperties)
                                    ParameterProperties[PropertieName].update(properties=ChildParameterProperties)
                                elif PropertyDataType == "array" and PropertyItems is not None:
                                    ItemDataType = PropertyItems["DataType"] if (PropertyItems.has_key("DataType")) else "string"
                                    Items = {
                                        "type": ItemDataType
                                    }
                                    if ItemDataType == "object":
                                        ItemProperties = PropertyItems["Properties"] if (PropertyItems.has_key("Properties")) else {}
                                        ItemParameterProperties = GenResponseProperties(ItemProperties)
                                        Items.update(properties=ItemParameterProperties)

                                    ParameterProperties[PropertieName].update(items=Items)

                            return ParameterProperties

                        ResponsesDescription = ApiResponse["Description"] if (ApiResponse.has_key("Description")) else "返回结果"
                        ResponsesDataType = ApiResponse["DataType"] if (ApiResponse.has_key("DataType")) else "string"
                        ResponsesProperties = ApiResponse["Properties"] if (ApiResponse.has_key("Properties")) else {}

                        Responses["200"]["schema"]["properties"]["resultData"]["description"] = ResponsesDescription
                        Responses["200"]["schema"]["properties"]["resultData"].update(type=ResponsesDataType)
                        if ResponsesDataType == "object" or ResponsesDataType == "array":
                            ResponseProperties = GenResponseProperties(ResponsesProperties)
                            Responses["200"]["schema"]["properties"]["resultData"].update(properties=ResponseProperties)

                    ApiSpecs["parameters"] = Parameters
                    ApiSpecs["responses"] = Responses
                    funcObject["ApiSpecs"] = ApiSpecs

                    for verb in funcObject["Methods"]:
                        self.SwaggerTemplate["paths"][func_url] = {
                            verb.lower(): ApiSpecs
                        }


                self.app.register_blueprint(plugin_blueprint, url_prefix="/"+plugin_name+"")
                '''暂时不使用该方法
                for funcName in plugin_funclist:
                    funcObject = plugin_funclist[funcName]
                    url = "/"+plugin_name+"/"+funcObject["ModuleName"]+""+funcName+""
                    self.app.add_url_rule(url,view_func=funcObject["ModuleFunc"], methods=["GET"])
                '''
            except Exception,e:
                logger.plugin.error("plugin package: {0} regedit,{1}".format(plugin["plugin_name"], e))
                logger.plugin.exception(e, exc_info=True)
                return
        self.Swagger = Swagger(self.app, template=self.SwaggerTemplate)

        #注册ApiJs
        blueprint = Blueprint("apijs", __name__)
        blueprint.add_url_rule('/api.js',methods=['GET'],view_func=self.__apijs_request)
        self.app.register_blueprint(blueprint, url_prefix="/apijs")

    #: 激活插件
    def ActivatePlugin(self):
        for plugin in self.plugins:
            try:
                plugin_instance = plugin["plugin_instance"]

                plugin_instance.Activate()
            except Exception,e:
                logger.plugin.error("plugin package: {0} active,{1}".format(plugin["pluginName"], e))
                logger.plugin.exception(e, exc_info=True)
                return

    #: 解除插件
    def DeactivatePlugin(self):
        for plugin in self.plugins:
            try:
                plugin_instance = plugin["plugin_instance"]

                plugin_instance.Deactivate()
            except Exception,e:
                logger.plugin.error("plugin package: {0} deactivate,{1}".format(plugin["pluginName"], e))
                logger.plugin.exception(e, exc_info=True)
                return

    #: 插件功能调度
    def __plugin_dispatch_request(self):
        result = {
            "resultCode": 0,
            "resultMsg": "成功",
            "resultData": None
        };

        try:
            path = request.path
            funcObject = self.plugins_functions.get(path)
            if funcObject is not None:
                funcObject = DynamicDict(funcObject)
                func = funcObject.ModuleFunc
                if func is not None:
                    #校验请求参数
                    validate(request.json, specs = funcObject.ApiSpecs, validation_error_handler=swagger_validation_error_handler)
                    result["resultData"] = func(DynamicDict(request.json))
                    #校验应答参数
                    #validate(result, specs = funcObject.ApiSpecs, validation_error_handler=swagger_validation_error_handler)
        except Exception, e:
            result["resultCode"] = -1
            result["resultMsg"] = "调用插件功能异常:{0}".format(e)
            result["resultData"] = None

        content = json.dumps(result, ensure_ascii=False)
        response = make_response(content)
        response.headers['Content-Type'] = 'application/json; charset=utf-8'
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.headers['Access-Control-Allow-Headers'] = 'x-requested-with,content-type'
        return response

    #: api js请求
    def __apijs_request(self):
        query = request.args
        PluginName = query.get('PluginName', '')
        ModuleName = query.get('ModuleName', '')

        #result = self.__GenApiJs(PluginName,ModuleName)
        #response = make_response(result)
        #response.content_type = 'application/javascript'

        ApiJsDict = self.__GenApiJsDict(PluginName,ModuleName)
        result = render_template('/apijs/api.js',ApiDict=ApiJsDict)
        response = make_response(result)
        response.content_type = 'application/javascript'

        return response

    # 生成api js脚本对象词典
    def __GenApiJsDict(self,PluginName,ModuleName):
        result = {
            "ApiInfo": {
                "AppName": GLOBAL.get('AppName'),
                "Description": GLOBAL.get('SwaggerDescription'),
                "Version": GLOBAL.get('Version')
            },
            "PluginList":[]
        }

        for plugin in self.plugins:
            plugin_name = plugin["plugin_name"]
            plugin_about = plugin["plugin_about"]
            plugin_funclist = plugin["plugin_funclist"]
            if len(PluginName) > 0 and plugin_name <> PluginName:
                continue

            plugin_info = None
            for funcName in plugin_funclist:
                funcObject = plugin_funclist[funcName]
                module_name = funcObject["ModuleName"]
                filepath, tmpfilename = os.path.split(funcObject["ModuleRule"])
                func_name, extension = os.path.splitext(tmpfilename)
                func_url = "{0}{1}/{2}{3}".format(request.host_url,plugin_name, funcObject["ModuleName"], funcObject["ModuleRule"])

                if len(ModuleName) > 0 and (plugin_name <> PluginName or module_name <> ModuleName):
                    continue

                if plugin_info is None:
                    plugin_info = {
                        "PluginName": plugin_name,
                        "PluginDescription": plugin_about["plugin_description"],
                        "Modules": {}
                    }

                if module_name not in plugin_info["Modules"]:
                    plugin_info["Modules"][module_name] = {
                        "ModuleName": module_name,
                        "FuncList": []
                    }

                plugin_info["Modules"][module_name]["FuncList"].append({
                    "FuncName": func_name,
                    "FuncUrl": func_url,
                    "FuncDescription": funcObject["ApiOperation"]["Description"]
                })

            if plugin_info is not None:
                result["PluginList"].append(plugin_info)

        return result

    # 生成api js脚本
    def __GenApiJs(self,PluginName,ModuleName):
        result = '/* \n'
        result += ' *' + GLOBAL.get('AppName') + ' \n'
        result += ' *' + GLOBAL.get('SwaggerDescription') + ' \n'
        result += ' *' + GLOBAL.get('Version') + ' \n'
        result += ' */ \n'

        result += 'var api = {}; \n'
        for plugin in self.plugins:
            plugin_name = plugin["plugin_name"]
            plugin_about = plugin["plugin_about"]
            plugin_funclist = plugin["plugin_funclist"]
            if len(PluginName) > 0 and plugin_name <> PluginName:
                continue

            result += '\r\n'
            result += '/* \n'
            result += ' *' + plugin_name + ' \n'
            result += ' *' + plugin_about["plugin_description"] + ' \n'
            result += ' */ \n'

            result += 'api.' + plugin_name + ' = {}; \n'

            ModuleNameList = []
            for funcName in plugin_funclist:
                funcObject = plugin_funclist[funcName]
                module_name = funcObject["ModuleName"]
                filepath, tmpfilename = os.path.split(funcObject["ModuleRule"])
                func_name, extension = os.path.splitext(tmpfilename)
                func_url = "{0}/{1}{2}".format(plugin_name, funcObject["ModuleName"], funcObject["ModuleRule"])

                if len(ModuleName) > 0 and (plugin_name <> PluginName or module_name <> ModuleName):
                    continue

                if module_name not in ModuleNameList:
                    result += 'api.' + plugin_name + '.' + module_name + ' = {}; \n'
                    ModuleNameList.append(module_name)

                result += '\r\n'
                result += '/* \n'
                result += ' *' + funcObject["ApiOperation"]["Description"] + ' \n'
                result += ' */ \n'

                result += 'api.' + plugin_name + '.' + module_name + '.' + module_name + ' = function (inParameter,success_callback,error_callback) { \n'
                result += ' $.ajax({ \n'
                result += '     url: "' + request.host_url + '' + func_url + '", \n'
                result += '     type: "POST", \n'
                result += '     async: true, \n'
                result += '     contentType: "application/json", \n'
                result += '     dataType: "json", \n'
                result += '     data: JSON.stringify(inParameter), \n'
                result += '     success: success_callback, \n'
                result += '     error: error_callback, \n'
                result += ' }); \n'
                result += '} \n'

        return result

def swagger_validation_error_handler(err, data, main_def):
    raise Exception(err.message)
